#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using JigLibX.Geometry;
using JigLibX.Math;
using System.Runtime.InteropServices;
#endregion

namespace JigLibX.Collision
{
    public class CollDetectSphereStaticMesh : DetectFunctor
    {

        public CollDetectSphereStaticMesh()
            : base("SphereStaticMesh", (int)PrimitiveType.Sphere, (int)PrimitiveType.TriangleMesh)
        {
        }

        public static void CollDetectSphereStaticMeshOverlap(Sphere oldSphere, Sphere newSphere,
            TriangleMesh mesh, CollDetectInfo info, float collTolerance, CollisionFunctor collisionFunctor)
        {
            Vector3 body0Pos = (info.Skin0.Owner != null) ? info.Skin0.Owner.OldPosition : Vector3.Zero;
            Vector3 body1Pos = (info.Skin1.Owner != null) ? info.Skin1.Owner.OldPosition : Vector3.Zero;

            float sphereTolR = collTolerance + newSphere.Radius;
            float sphereTolR2 = sphereTolR * sphereTolR;

            List<CollPointInfo> collPts = new List<CollPointInfo>();
            Vector3 collNormal = Vector3.Zero;

            AABox sphereBox = new AABox();
            sphereBox.AddSphere(newSphere);
            List<int> potentialTriangles = new List<int>();
            int numTriangles = mesh.GetTrianglesIntersectingtAABox(potentialTriangles, sphereBox);

            for (int iTriangle = 0; iTriangle < numTriangles; ++iTriangle)
            {
                IndexedTriangle meshTriangle = mesh.GetTriangle(potentialTriangles[iTriangle]);
                float distToCentre = Distance.PointPlaneDistance(newSphere.Position, meshTriangle.Plane);

                if (distToCentre <= 0.0f)
                    continue;
                if (distToCentre >= sphereTolR2)
                    continue;
                int i0, i1, i2;
                meshTriangle.GetVertexIndices(out i0, out i1, out i2);

                Triangle triangle = new Triangle(mesh.GetVertex(i0), mesh.GetVertex(i1), mesh.GetVertex(i2));

                float s, t;
                float newD2 = Distance.PointTriangleDistanceSq(out s, out t, newSphere.Position, triangle);

                if (newD2 < sphereTolR2)
                {
                    // have overlap - but actually report the old intersection
                    float oldD2 = Distance.PointTriangleDistanceSq(out s, out t, oldSphere.Position, triangle);
                    float dist = (float)System.Math.Sqrt((float)oldD2);
                    float depth = oldSphere.Radius - dist;

                    Vector3 triPointSTNorm = oldSphere.Position -triangle.GetPoint(s,t);
                    if (triPointSTNorm.LengthSquared() > 0.0f)
                        triPointSTNorm.Normalize();

                    Vector3 collisionN = (dist > float.Epsilon) ? triPointSTNorm : triangle.Normal;

                    // since impulse get applied at the old position
                    Vector3 pt = oldSphere.Position - oldSphere.Radius * collisionN;
                    collPts.Add(new CollPointInfo(pt - body0Pos, pt - body1Pos, depth));
                    collNormal += collisionN;
                }
            }

            if (collPts.Count > 0)
            {
                if (collNormal.LengthSquared() > 0)
                    collNormal.Normalize();
                collisionFunctor.CollisionNotify(info, collNormal, collPts);
            }
        }

        private void CollDetectOverlap(CollDetectInfo info, float collTolerance, CollisionFunctor collisionFunctor)
        {
            // todo - proper swept test
            Sphere oldSphere = info.Skin0.GetPrimitiveOldWorld(info.IndexPrim0) as Sphere;
            Sphere newSphere = info.Skin0.GetPrimitiveNewWorld(info.IndexPrim0) as Sphere;

            // todo - proper swept test
            // note - mesh is static and its triangles are in world space
            TriangleMesh mesh = info.Skin1.GetPrimitiveNewWorld(info.IndexPrim1) as TriangleMesh;

            CollDetectSphereStaticMeshOverlap(oldSphere, newSphere, mesh, info, collTolerance, collisionFunctor);
        }

        public static void CollDetectSphereStaticMeshSweep(Sphere oldSphere, Sphere newSphere, TriangleMesh mesh,
            CollDetectInfo info, float collTolerance, CollisionFunctor collisionFunctor)
        {

            // really use a swept test - or overlap?
            Vector3 delta = newSphere.Position - oldSphere.Position;
            if (delta.LengthSquared() < (0.25f * newSphere.Radius * newSphere.Radius))
            {
                CollDetectSphereStaticMeshOverlap(oldSphere, newSphere, mesh, info, collTolerance, collisionFunctor);
            }
            else
            {
                Vector3 body0Pos = (info.Skin0.Owner != null) ? info.Skin0.Owner.OldPosition : Vector3.Zero;
                Vector3 body1Pos = (info.Skin1.Owner != null) ? info.Skin1.Owner.OldPosition : Vector3.Zero;

            float sphereTolR = collTolerance + oldSphere.Radius;
            float sphereToR2 = sphereTolR * sphereTolR;

            List<CollPointInfo> collPts = new List<CollPointInfo>();
            Vector3 collNormal = Vector3.Zero;

            AABox sphereBox = new AABox();
            sphereBox.AddSphere(oldSphere);
            sphereBox.AddSphere(newSphere);

            List<int> potentialTriangles = new List<int>();
            int numTriangles = mesh.GetTrianglesIntersectingtAABox(potentialTriangles, sphereBox);

            for (int iTriangle = 0; iTriangle < numTriangles; ++iTriangle)
            {

                // first test the old sphere for being on the wrong side
                IndexedTriangle meshTriangle = mesh.GetTriangle(potentialTriangles[iTriangle]);
                float distToCentreOld = Distance.PointPlaneDistance(oldSphere.Position, meshTriangle.Plane);
                if (distToCentreOld <= 0.0f)
                    continue;
                // now test the new sphere for being clear
                float distToCentreNew = Distance.PointPlaneDistance(newSphere.Position, meshTriangle.Plane);
                if (distToCentreNew > sphereTolR)
                    continue;

                int i0, i1, i2;
                meshTriangle.GetVertexIndices(out i0, out i1, out i2);

                Triangle triangle = new Triangle(mesh.GetVertex(i0), mesh.GetVertex(i1), mesh.GetVertex(i2));

                // If the old sphere is intersecting, just use that result
                float s, t;
                float d2 = Distance.PointTriangleDistanceSq(out s, out t, oldSphere.Position,triangle);

                if (d2 < sphereToR2)
                {
                    float dist = (float)System.Math.Sqrt(d2);
                    float depth = oldSphere.Radius - dist;
                    Vector3 triangleN = triangle.Normal;
                    Vector3 normSafe = oldSphere.Position - triangle.GetPoint(s,t);

                    if(normSafe.LengthSquared() > 0.0f)
                        normSafe.Normalize();

                    Vector3 collisionN = (dist > float.Epsilon) ? normSafe : triangleN;
                    // since impulse gets applied at the old position
                    Vector3 pt = oldSphere.Position - oldSphere.Radius * collisionN;
                    collPts.Add(new CollPointInfo(pt - body0Pos, pt - body1Pos, depth));
                    collNormal += collisionN;
                }
                else if(distToCentreNew < distToCentreOld)
                {
                    // old sphere is not intersecting - do a sweep, but only if the sphere is moving into the
                    // triangle
                    Vector3 pt,N; // CHECK THIS
                    float depth;
                    if(Intersection.SweptSphereTriangleIntersection(out pt,out N,out depth,oldSphere,newSphere, triangle,
                        distToCentreOld,distToCentreNew,Intersection.EdgesToTest.EdgeAll, Intersection.CornersToTest.CornerAll))
                    {
                        // collision point etc must be relative to the old position because that's
                        //where the impulses are applied
                        float dist = (float) System.Math.Sqrt(d2);
                        float depth2 = oldSphere.Radius - dist;
                        Vector3 triangleN = triangle.Normal;
                        Vector3 normSafe = oldSphere.Position - triangle.GetPoint(s,t);
                        if(normSafe.LengthSquared() > 0.0f) normSafe.Normalize();
                        Vector3 collisionN = (dist > JiggleMath.Epsilon) ? normSafe : triangleN;
                        // since impulse gets applied at the old position
                        Vector3 pt2 = oldSphere.Position - oldSphere.Radius * collisionN;
                        collPts.Add(new CollPointInfo(pt2 - body0Pos, pt2 - body1Pos,depth));
                        collNormal += collisionN;
                    }
                }
            }
                if(collPts.Count > 0)
                {
                    if(collNormal.LengthSquared() > 0.0f) collNormal.Normalize();
                    collisionFunctor.CollisionNotify(info,collNormal,collPts);
                }
            }
        }

        private void CollDetectSweep(CollDetectInfo info, float collTolerance,
                                                  CollisionFunctor collisionFunctor)
        {
            // todo - proper swept test
            Sphere oldSphere = info.Skin0.GetPrimitiveOldWorld(info.IndexPrim0) as Sphere;
            Sphere newSphere = info.Skin0.GetPrimitiveNewWorld(info.IndexPrim0) as Sphere;

            // todo - proper swept test
            // note - mesh is static and its triangles are in world space
            TriangleMesh mesh = info.Skin1.GetPrimitiveNewWorld(info.IndexPrim1) as TriangleMesh;

            CollDetectSphereStaticMeshSweep(oldSphere, newSphere, mesh, info, collTolerance, collisionFunctor);
        }

        public override void CollDetect(CollDetectInfo info, float collTolerance, CollisionFunctor collisionFunctor)
        {
            if (info.Skin0.GetPrimitiveOldWorld(info.IndexPrim0).Type == this.Type1)
            {
                CollisionSkin skinSwap = info.Skin0;
                info.Skin0 = info.Skin1;
                info.Skin1 = skinSwap;
                int primSwap = info.IndexPrim0;
                info.IndexPrim0 = info.IndexPrim1;
                info.IndexPrim1 = primSwap;
            }

            if(info.Skin0.CollisionSystem != null && info.Skin0.CollisionSystem.UseSweepTests)
                CollDetectSweep(info, collTolerance, collisionFunctor);
            else
                CollDetectOverlap(info, collTolerance, collisionFunctor);
        }
    }
}
